/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.std.memory;

import java.awt.Color;
import java.awt.Graphics;
import java.io.File;
import java.io.IOException;
import java.util.WeakHashMap;

import com.cburch.hex.HexModel;
import com.cburch.hex.HexModelListener;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.gui.hex.HexFile;
import com.cburch.logisim.gui.hex.HexFrame;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.tools.MenuExtender;
import com.cburch.logisim.tools.key.BitWidthConfigurator;
import com.cburch.logisim.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringGetter;
import com.cburch.logisim.util.StringUtil;

abstract class Mem extends InstanceFactory {
	static class MemListener implements HexModelListener {
		Instance instance;

		MemListener(Instance instance) {
			this.instance = instance;
		}

		@Override
		public void bytesChanged(HexModel source, long start, long numBytes, int[] values) {
			instance.fireInvalidated();
		}

		@Override
		public void metainfoChanged(HexModel source) {
		}
	}

	// Note: The code is meant to be able to handle up to 32-bit addresses, but
	// it
	// hasn't been debugged thoroughly. There are two definite changes I would
	// make if I were to extend the address bits: First, there would need to be
	// some
	// modification to the memory's graphical representation, because there
	// isn't
	// room in the box to include such long memory addresses with the current
	// font
	// size. And second, I'd alter the MemContents class's PAGE_SIZE_BITS
	// constant
	// to 14 so that its "page table" isn't quite so big.
	public static final Attribute<BitWidth> ADDR_ATTR = Attributes.forBitWidth("addrWidth",
			Strings.getter("ramAddrWidthAttr"), 2, 24);

	public static final Attribute<BitWidth> DATA_ATTR = Attributes.forBitWidth("dataWidth",
			Strings.getter("ramDataWidthAttr"));
	static final AttributeOption SEL_HIGH = new AttributeOption("high", Strings.getter("stdTriggerHigh"));

	static final AttributeOption SEL_LOW = new AttributeOption("low", Strings.getter("stdTriggerLow"));

	static final Attribute<AttributeOption> ATTR_SELECTION = Attributes.forOption("Select",
			Strings.getter("ramSelAttr"), new AttributeOption[] { SEL_HIGH, SEL_LOW });
	// port-related constants
	static final int DATA = 0;
	static final int ADDR = 1;
	static final int CS = 2;

	static final int MEM_INPUTS = 3;

	// other constants
	static final int DELAY = 10;

	private WeakHashMap<Instance, File> currentInstanceFiles;

	Mem(String name, StringGetter desc, int extraPorts) {
		super(name, desc);
		currentInstanceFiles = new WeakHashMap<Instance, File>();
		setInstancePoker(MemPoker.class);
		setKeyConfigurator(JoinedConfigurator.create(new BitWidthConfigurator(ADDR_ATTR, 2, 24, 0),
				new BitWidthConfigurator(DATA_ATTR)));

		setOffsetBounds(Bounds.create(-140, -40, 140, 80));
	}

	@Override
	protected void configureNewInstance(Instance instance) {
		Bounds bds = instance.getBounds();
		instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT, StdAttr.ATTR_LABEL_COLOR,
				bds.getX() + bds.getWidth() / 2, bds.getY() - 3, GraphicsUtil.H_CENTER, GraphicsUtil.V_BOTTOM);
		configurePorts(instance);
	}

	abstract void configurePorts(Instance instance);

	void configureStandardPorts(Instance instance, Port[] ps) {
		ps[DATA] = new Port(0, 0, Port.OUTPUT, DATA_ATTR);
		ps[ADDR] = new Port(-140, 0, Port.INPUT, ADDR_ATTR);
		ps[CS] = new Port(-90, 40, Port.INPUT, 1);
		ps[DATA].setToolTip(Strings.getter("memDataTip"));
		ps[ADDR].setToolTip(Strings.getter("memAddrTip"));
	}

	@Override
	public abstract AttributeSet createAttributeSet();

	File getCurrentImage(Instance instance) {
		return currentInstanceFiles.get(instance);
	}

	abstract HexFrame getHexFrame(Project proj, Instance instance, CircuitState state);

	@Override
	protected Object getInstanceFeature(Instance instance, Object key) {
		if (key == MenuExtender.class)
			return new MemMenu(this, instance);
		return super.getInstanceFeature(instance, key);
	}

	abstract MemState getState(Instance instance, CircuitState state);

	abstract MemState getState(InstanceState state);

	public void loadImage(InstanceState instanceState, File imageFile) throws IOException {
		MemState s = this.getState(instanceState);
		HexFile.open(s.getContents(), imageFile);
		this.setCurrentImage(instanceState.getInstance(), imageFile);
	}

	@Override
	public void paintInstance(InstancePainter painter) {
		Graphics g = painter.getGraphics();
		Bounds bds = painter.getBounds();

		// draw boundary
		painter.drawRoundBounds(Color.WHITE);

		// draw contents
		if (painter.getShowState()) {
			MemState state = getState(painter);
			state.paint(painter.getGraphics(), bds.getX(), bds.getY());
		} else {
			BitWidth addr = painter.getAttributeValue(ADDR_ATTR);
			int addrBits = addr.getWidth();
			int bytes = 1 << addrBits;
			String label;
			if (this instanceof Rom) {
				if (addrBits >= 30) {
					label = StringUtil.format(Strings.get("romGigabyteLabel"), "" + (bytes >>> 30));
				} else if (addrBits >= 20) {
					label = StringUtil.format(Strings.get("romMegabyteLabel"), "" + (bytes >> 20));
				} else if (addrBits >= 10) {
					label = StringUtil.format(Strings.get("romKilobyteLabel"), "" + (bytes >> 10));
				} else {
					label = StringUtil.format(Strings.get("romByteLabel"), "" + bytes);
				}
			} else {
				if (addrBits >= 30) {
					label = StringUtil.format(Strings.get("ramGigabyteLabel"), "" + (bytes >>> 30));
				} else if (addrBits >= 20) {
					label = StringUtil.format(Strings.get("ramMegabyteLabel"), "" + (bytes >> 20));
				} else if (addrBits >= 10) {
					label = StringUtil.format(Strings.get("ramKilobyteLabel"), "" + (bytes >> 10));
				} else {
					label = StringUtil.format(Strings.get("ramByteLabel"), "" + bytes);
				}
			}
			GraphicsUtil.drawCenteredText(g, label, bds.getX() + bds.getWidth() / 2, bds.getY() + bds.getHeight() / 2);
		}

		// draw input and output ports
		painter.drawPort(DATA, Strings.get("ramDataLabel"), Direction.WEST);
		painter.drawPort(ADDR, Strings.get("ramAddrLabel"), Direction.EAST);
		g.setColor(Color.GRAY);
		painter.drawPort(CS, Strings.get("ramCSLabel"), Direction.SOUTH);
		painter.drawLabel();
	}

	@Override
	public abstract void propagate(InstanceState state);

	void setCurrentImage(Instance instance, File value) {
		currentInstanceFiles.put(instance, value);
	}
}
